package org.scisaga.tetra.core;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.HashMap;
import java.util.UUID;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

/**
 * Remote Entity Object
 * @author karajan
 *
 */
public class EntiImpl {
	
	/**
	 * 
	 */
	protected String id;
	protected String name;
	protected String graph_id;
	protected String DBUrl;
	protected String username;
	protected String passwd;
	protected HashMap<String,String> varList;
	
	private boolean initSuccess = true;
	
	String field;
	
	//
	protected Connection conn;
	protected Statement stmt;
	
	public static Messager mgr;
	
	/**
	 * Initial a Entity by a JSON object
	 */
	@SuppressWarnings("unchecked")
	public EntiImpl(JSONObject e){
		
		varList = new HashMap<String, String>();
		try {
			id = e.getString("id");
			name = e.getString("name");
			// e.g. TestStage
			graph_id = e.getString("graph_id");
			// e.g. TestRun
			DBUrl = e.getString("DBUrl");
			// e.g. tcp://166.111.59.15:12358/
			username = e.getString("username");
			passwd = e.getString("passwd");
			
			String key = null;
			JSONObject var_list = e.getJSONObject("varList");
			for(java.util.Iterator<String> iter = var_list.keys(); iter.hasNext(); ){
				key = iter.next();
				varList.put(key,var_list.getString(key));
			}
			field = "";
			for(String s : varList.keySet()){
				field += s.substring(1, s.length());; field += ",";
			}
			field = field.substring(0, field.length()-1);
		} catch (JSONException ex) {
			//ex.printStackTrace();
			initSuccess = false;
			log("Entity() -> Input JSON String is invalid");
		}
	}
	
	/**
	 * Try to connect DB
	 * @return
	 */
	public boolean tryDB(){
		try {
			Class.forName("org.h2.Driver");
			conn = DriverManager.getConnection("jdbc:h2:" + DBUrl + "store/" + graph_id, username, passwd);
			stmt = conn.createStatement();
		} catch (SQLException e) {
			//e.printStackTrace();
			log("tryDB() -> DB Connection Exception");
			return false;
		} catch (ClassNotFoundException e) {
			//e.printStackTrace();
			log("tryDB() -> DB Connector ClassNotFoundException");
		}
		return true;
	}
	
	/**
	 * Register the Entity in DB
	 * @return
	 */
	public boolean reg(){
		String field = "id UUID,";
		for(String s : varList.keySet()){
			field += s.substring(1, s.length()); field += " "; field += varList.get(s); field += ", ";
		}
		field += "Timestamp TIMESTAMP";
		try {
			stmt.executeUpdate("CREATE TABLE IF NOT EXISTS " + name + "(" + field + ")");
		} catch (SQLException e) {
			e.printStackTrace();
			log("init() -> SQLException");
			return false;
		}
		return true;
	}
	
	/**
	 * Check if this entity is initialized success
	 * @return
	 */
	public boolean initSuccess(){
		if(this.initSuccess){
			return true;
		} else return false;
	}
	
	/**
	 * Put a token into DB
	 * @param token
	 * @return
	 */
	public boolean putToken(HashMap<String, String> token){
		
		//log(token);
		//System.out.println(this.name);
		//for(String key: token.keySet()){
		//	System.out.print("["+key+"->"+token.get(key)+"]");
		//}
		//System.out.println();
		
		UUID id = UUID.randomUUID(); 
		// TODO: check if token's keySet equals varList's keySet
		
		String sql = "INSERT INTO " + name + " (id," + field + ",Timestamp) VALUES (\'" + id + "\',";
		try{
			for(String s : token.keySet()){
				
				if(varList.get(s).equals("INT")){
					sql += Integer.parseInt(token.get(s));
					sql += ",";
				} else if(varList.get(s).equals("DOUBLE")){
					sql += Double.parseDouble(token.get(s));
					sql += ",";
				} else if(varList.get(s).equals("REAL")){
					sql += Float.parseFloat(token.get(s));
					sql += ",";
				} else {
					sql += ("\'" + token.get(s) +"\'");
					sql += ",";
				}
			}
		} catch (NullPointerException e){
			log("Input token does not fit Entity token type");
		}
		
		sql += "\'" + new java.sql.Timestamp(System.currentTimeMillis()) + "\')";
		//System.out.println(sql);
		try {
			stmt.executeUpdate(sql);
			return true;
		} catch (SQLException e) {
			e.printStackTrace();
			return false;
		}
		
	}
	
	public JSONArray getAllTokens(){
		String sql = "SELECT id," + field + ",Timestamp FROM " + name;
		ResultSet rs;
		JSONArray outputTokens = new JSONArray();
		try {
			rs = stmt.executeQuery(sql);
			while(rs.next()){
				// Get token
				HashMap<String,String> token = new HashMap<String,String>();
				for(String key : varList.keySet()){
					token.put(key, rs.getString(key.substring(1,key.length())));
				}
				// Insert the Timestamp
				token.put("Timestamp", rs.getString("Timestamp"));
				JSONObject tokenJSON = new JSONObject(token);
				outputTokens.put(tokenJSON);
			}
			
		} catch (SQLException e) {
			log("*ERROR* getAllTokens()");
			e.printStackTrace();
		}
		try {
			stmt.executeUpdate("TRUNCATE TABLE " + name);
		} catch (SQLException e) {
			log("*ERROR* getAllTokens() TRUNCATE TABLE");
			e.printStackTrace();
		}
		return outputTokens;
	}
	
	/**
	 * Close DB link
	 */
	public void close(){
		try {
			//stmt.executeUpdate("TRUNCATE TABLE " + this.name);
			stmt.executeUpdate("DROP TABLE IF EXISTS " + this.name);
			//TODO : should drop database if it contains no table
			
			stmt.close();
			conn.close();
		} catch (SQLException e) {
			e.printStackTrace();
		}
	}
	
	public String getDBUrl(){
		return this.DBUrl;
	}
	
	public String getId(){
		return this.id;
	}
	
	public String getName(){
		return this.name;
	}
	
	public int getTokenNum(){
		int totalNum = 0;
		try {
			Statement stmt = conn.createStatement();
			ResultSet rs = stmt.executeQuery("SELECT COUNT(*) FROM " + name);
			if(rs.next()){
				totalNum = rs.getInt(1);
			}
			rs.close();
			stmt.close();
		} catch (SQLException e) {
			// log()
			e.printStackTrace();
		}
		return totalNum;
	}
	
	/**
	 * log
	 * @param s
	 */
	private static void log(String s){
		if(mgr != null){
			mgr.computeNode.log("Entity :: " + s);
		} else System.out.println("Entity :: " + s);
	}
	
	/**
	 * log
	 * @param token
	 */
	private static void log(HashMap<String,String> token){
		if(token.isEmpty()) log("No Available Tokens");
		String output = "";
		for(String s : token.keySet()){
			output += "["+s+","+token.get(s)+"]";
		}
		log(output);
	}
	
	/**
	 * Main test function
	 * @param argv
	 */
	public static void main(String[] argv){
		log("A simple entity usage test");
		String s = "{\"id\":\"testEntity_20100405_0\",";
		s += "\"name\":\"testEntity_0\",";
		s += "\"graph_id\":\"testNet\",";
		s += "\"DBUrl\":\"tcp://localhost:12358/\",";
		s += "\"username\":\"\",";
		s += "\"passwd\":\"\",";
		s += "\"varList\":{\"$x1\":\"INT\",\"$x2\":\"INT\"}}";
		JSONObject json;
		try {
			json = new JSONObject(s);
			EntiImpl e = new EntiImpl(json);
			if(! e.tryDB()) log("Database Connection Error : " + e.DBUrl);
			if(! e.reg()) log("Can't create table : " + e.name + "@" + e.DBUrl);
			
			// Write and Read Test
			for(int i=0; i<1000; i++){
				HashMap<String,String> token = new HashMap<String,String>();
				token.put("$x1", String.valueOf(i));
				token.put("$x2", String.valueOf(i+1));
				e.putToken(token);
				log(token);
			}
		} catch (JSONException e) {
			e.printStackTrace();
		}
	}
	
}